CALLING C FROM OPL ================== There are essentially three ways to call C code from OPL: a) Load the code into memory and execute it using the OPL keyword USR. b) Build the code into a DYL (dynamic link library). Load it using the LibLoad OS call. Create the objects using the LibCreateByHandle OS call. Then send messages to the created objects using the LibSend OS call. c) Build the code into a logical device driver (LDD). Load it using the DevLoadLDDusing OS call. Open a channel to the device using the OPL keyword IOOPEN. Execute the code using the OPL keywords IOW and IOA. This document describes the first method. This is the least flexible of the three, however it is the easiest to get going. There are two stages to the process. First you must produce a binary file containing the code to be executed. Then you must load the code in your OPL module and execute it. Producing the binary executable file ------------------------------------ It is a straightforward process to produce the binary executable. You write your C module and then compile it in the normal manner. You must then link it, but without a startup module. Unfortunately I have not yet worked out the runes to do this using the JPI linker, however it is easy to do it using the Turbo linker, just type TLINK NOTE: when you link your C program you will get a NO STACK warning, this is fine, since you stack will be provided by the OPL program (SYS$PRGO to be precise) Finally you use EXE2BIN to convert the .EXE file to a .BIN file. There are, however some provisos: Firstly the procedure to be called from OPL must return with a RETF (ret far) instruction, this is accomplished using the JPI pragma: #pragma call(seg_name=>null,near_call=>off) Secondly, your module must not contain any static data. That is to say any data used by you program must either be on the stack, be allocated using p_alloc(), or have been reserved for you by the calling OPL program. Note that this also means YOU MAY NOT HAVE ANY QUOTED STRINGS (ie "Hello World"). This is because in an OPL program DS points to the OPL's data segment (the data segment of SYS$PRGO to be precise), and so any static data will be inaccessible. Thirdly, although you know the address of the first procedure in the module, you will not know the address of any of the other procedures. I therefore suggest that, if you wish to call more than one procedure, the first procedure should contain a switch statement selecting which procedure to run: #pragma save #pragma call(seg_name=>null,near_call=>off) GLDEF_C INT select(INT procno,INT p1,INT p2,INT p3) /* Must return with a retf (ret far), since called from OPL */ { switch (procno) { case 1: return (f1(p1,p2,p3)); case 2: return (f2(p1,p2,p3)); } return (-1); } #pragma restore Note that USR passes (up to) 4 parameters to the called code, they are in AX..DX which is where the JPI compiler expects to find them (see the reg_params pragma in OPL.C). OPL.C is a full working example of a C program that can be called from OPL. Loading the binary executable from OPL -------------------------------------- There are three steps: Firstly reserve some memory in which to load the code, this is most easily done by declaring an array of the appropriate size. Secondly load the binary file (produced above) into this array. Thirdly use USR to execute the code. All the above are illustrated in the file CALLC.OPL. P_STD.H ------- For those of you who do not have the Psion SDK here is an extract that should allow you to compile OPL.C #define P_STD_H #define GLREF_D extern #define GLDEF_D #define LOCAL_D static #define GLREF_C extern #define LOCAL_C static #define GLDEF_C #define FOREVER for(;;) #define TRUE 1 #define FALSE 0 #define NULL 0 #define VOID void #define FAST register typedef int INT,HANDLE; typedef unsigned int UINT; typedef signed char BYTE; typedef unsigned char UBYTE; typedef short int WORD; typedef unsigned short int UWORD; typedef long int LONG; typedef unsigned long int ULONG; typedef double DOUBLE; typedef float FLOAT; typedef unsigned char TEXT; END OF DOCUMENT